"""

"""

from .constants import (IAC, COMMAND, COMMAND_TO_STRING, OPTION_TO_STRING,
                        OPERATIONS)


class IACSequence(object):
    """
    A class representing an IAC sequence, there are two possible sequences,
    where each field is a single byte:
    
    Operation:
        
        +-----+---------+--------+
        | IAC | COMMAND | OPTION |
        +-----+---------+--------+
    
    Where COMMAND is one of WILL, WONT, DO, DONT.
    
    Suboption negotiation:
        
        +-----+----+--------+------------+-----+----+
        | IAC | SB | OPTION | data bytes | IAC | SE |
        +-----+----+--------+------------+-----+----+
    
    The values for these are in constants.py
    """
    
    def __init__(self, command=None, option=None, data=None):
        self.command = command
        self.option = option
        self.data = data
    
    def __repr__(self):
        properties = ""
        if self.command is not None:
            command = COMMAND_TO_STRING.get(self.command, ord(self.command))
            properties += " command={0}".format(command)
        if self.option is not None:
            option = OPTION_TO_STRING.get(self.option, ord(self.option))
            properties += " option={0}".format(option)
        if self.data is not None:
            properties += " data='{0}'".format(repr(self.data))
        return "<IACSequence{0}>".format(properties)


class TelnetProtocol(object):
    """
    This class is used to parse IAC sequences out of a stream of telnet data.
    
    Here's how to use TelnetProtocol:
        
        tp = TelnetProtocol(iac_callback)
        data_out = tp(data_in)
        more_data_out = tp(more_data_in)
    
    Whenever the TelnetProtocol encounters an IAC sequence, the iac_callback
    function is called with an IACSequence object as its sole argument.
    
    There are perhaps more clear ways to implement this, but it is not too
    long and a bit faster this way.
    
    This is not a full implementation of the Telnet protocol, for instance, it
    is character mode, not line mode, and there is no Go Ahead message, but it
    should work on any modern server.
    """
    
    def __init__(self, iac_callback=None):
        if iac_callback is None:
            def iac_callback(x):
                pass
        self.iac_callback = iac_callback
        self.state = 0
        self.current_iac_sequence = None
    
    def __call__(self, data):
        position = 0
        buffers = []
        state = self.state
        while position < len(data):
            if state == 0:
                # text
                find_index = data.find(IAC, position)
                if find_index == -1:
                    data_out = data[position:]
                    position = len(data)
                else:
                    data_out = data[position:find_index]
                    position = find_index + 1
                    state = 1
                # replace NULL bytes
                data_out = data_out.replace('\x00','')
                buffers.append(data_out)
            elif state == 1:
                # start of iac sequence
                byte = data[position]
                position += 1
                if byte == IAC:
                    # oops, not really an IAC, just a doubled IAC character
                    # carry on
                    buffers.append(byte)
                    state = 0
                else:
                    self.current_iac_sequence = IACSequence()
                    self.current_iac_sequence.command = byte
                    if byte in OPERATIONS or byte == COMMAND['SB']:
                        # still one more byte to indicate what option
                        state = 2
                    else:
                        # end of the iac sequence, send it out
                        self.iac_callback(self.current_iac_sequence)
                        state = 0
            elif state == 2:
                # normal 3-byte IAC sequence, get the last byte and
                # we're done
                byte = data[position]
                position += 1
                self.current_iac_sequence.option = byte
                if self.current_iac_sequence.command == COMMAND['SB']:
                    self.current_iac_sequence.data = ""
                    # time for a suboption negotiation
                    state = 3
                else:
                    # end of the iac sequence, send it out
                    self.iac_callback(self.current_iac_sequence)
                    state = 0
            elif state == 3:
                # suboption negotiation (IAC+SB), keep appending bytes until
                # we find another IAC sequence that may end it
                byte = data[position]
                position += 1
                if byte == IAC:
                    state = 4
                else:
                    self.current_iac_sequence.data += byte
            elif state == 4:
                # end of suboption negotiation
                byte = data[position]
                position += 1
                if byte == COMMAND['SE']:
                    # end of the suboption, send out the iac sequence
                    self.iac_callback(self.current_iac_sequence)
                    state = 0
                else:
                    # oops, not really the end, apppend the bytes and go back
                    if byte == IAC:
                        # doubled IAC, only put the IAC on there, not two of them
                        byte = ''
                    self.current_iac_sequence.data += IAC + byte
                    state = 3
            else:
                raise Exception("invalid state {0}".format(state))
        self.state = state
        return ''.join(buffers)